﻿using AutoMapper;
using JESAI.AutoMapper.Attributes;
using JESAI.AutoMapper.Configurations;
using JESAI.AutoMapper.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using AutoMapAttribute = JESAI.AutoMapper.Attributes.AutoMapAttribute;

namespace JESAI.AutoMapper.DependencyInjection
{
    public static class AutoMapperServiceCollectionExtensions 
    {
        private static readonly object _syncObj = new object();
        private static Type[] _types;
        public static IServiceCollection AddAutoMapper(this IServiceCollection services, Assembly[] assemblies)
         {      
            RegisterServices(services);
            CreateMappings(services,assemblies);
            return services;
         }  

        private static void RegisterServices(IServiceCollection services)
        {
            services.AddSingleton<IObjectMapper, AutoMapperObjectMapper>();
            services.AddSingleton<IAutoMapperConfiguration, AutoMapperConfiguration>();
        }
        private static void CreateMappings(IServiceCollection services, Assembly[] assemblies)
        {
            Action<IMapperConfigurationExpression> configurer = configuration =>
            {
                FindAndAutoMapTypes(configuration, assemblies);
                IAutoMapperConfiguration autoMapperConfiguration = services.BuildServiceProvider().GetService<IAutoMapperConfiguration>();
                foreach (var configurator in autoMapperConfiguration.Configurators)
                {
                    configurator(configuration);
                }
            };

            var config = new MapperConfiguration(configurer);
            services.AddSingleton<IConfigurationProvider> (config);

            var mapper = config.CreateMapper();
            services.AddSingleton<IMapper>(mapper);
            EmulateAutoMapper.Mapper = mapper;
        }
        private static void FindAndAutoMapTypes(IMapperConfigurationExpression configuration, Assembly[] assemblies)
        {
            var types = Find(type =>
            {
                var typeInfo = type.GetTypeInfo();
                return typeInfo.IsDefined(typeof(AutoMapAttribute)) ||
                       typeInfo.IsDefined(typeof(AutoMapFromAttribute)) ||
                       typeInfo.IsDefined(typeof(AutoMapToAttribute));
            }, assemblies);

            foreach (var type in types)
            {
                configuration.CreateAutoAttributeMaps(type);
            }
        }

        public static Type[] Find(Func<Type, bool> predicate, Assembly[] assemblies)
        {
            return GetAllTypes(assemblies).Where(predicate).ToArray();
        }



        private static Type[] GetAllTypes(Assembly[] assemblies)
        {
            if (_types == null)
            {
                lock (_syncObj)
                {
                    if (_types == null)
                    {
                        _types = CreateTypeList(assemblies).ToArray();
                    }
                }
            }

            return _types;
        }
      
        private static List<Type> CreateTypeList(Assembly[] assemblies)
        {
            var allTypes = new List<Type>();          

            foreach (var assembly in assemblies)
            {
                try
                {
                    Type[] typesInThisAssembly;

                    try
                    {
                        typesInThisAssembly = assembly.GetTypes();
                    }
                    catch (ReflectionTypeLoadException ex)
                    {
                        typesInThisAssembly = ex.Types;
                    }

                    if (typesInThisAssembly.IsNullOrEmpty())
                    {
                        continue;
                    }

                    allTypes.AddRange(typesInThisAssembly.Where(type => type != null));
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

            return allTypes;
        }
        public static IApplicationBuilder UseAutoMapper(this IApplicationBuilder app)
        {      
            return app;
        }
    }
}